package com.psytech.app.task.service.implementation;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import taskmanager.psytech.com.taskmanagerservice._1.Comment;
import taskmanager.psytech.com.taskmanagerservice._1.GetCommentsResponse.Comments;
import taskmanager.psytech.com.taskmanagerservice._1.GetTasksResponse.Tasks;
import taskmanager.psytech.com.taskmanagerservice._1.GetUsersResponse.Users;
import taskmanager.psytech.com.taskmanagerservice._1.ObjectFactory;
import taskmanager.psytech.com.taskmanagerservice._1.PriorityType;
import taskmanager.psytech.com.taskmanagerservice._1.StatusType;
import taskmanager.psytech.com.taskmanagerservice._1.Task;
import taskmanager.psytech.com.taskmanagerservice._1.User;

import com.psytech.app.task.service.TaskManagerService;
import com.psytech.app.task.service.exception.BusinessException;
import com.psytech.app.task.service.exception.TechnicalException;

public class TaskManagerServiceImpl extends AbstractService implements TaskManagerService {

	private static final Logger LOG = Logger.getLogger(TaskManagerServiceImpl.class);

	private static final ObjectFactory FACTORY = new ObjectFactory();

	@Override
	public Comment createComment(final Comment comment) throws BusinessException, TechnicalException {
		final com.psytech.app.task.entity.implementation.Comment commentToCreate = this.getEntityComment(comment);
		commentToCreate.setWriter(this.getDao().findUserByUid(commentToCreate.getWriter().getUid()));
		commentToCreate.setTask(this.getDao().findTaskByUid(commentToCreate.getTask().getUid()));
		commentToCreate.setCreationDate(new Date());
		commentToCreate.setUid(UUID.randomUUID().toString());
		this.getDao().create(commentToCreate);
		commentToCreate.getWriter().getComments().add(commentToCreate);
		this.getDao().save(commentToCreate.getWriter());
		commentToCreate.getTask().getComments().add(commentToCreate);
		this.getDao().save(commentToCreate.getTask());
		return this.getJaxBComment(commentToCreate);
	}

	@Override
	public void deleteComment(final Comment comment) throws BusinessException, TechnicalException {
		if (comment.getUid() != null) {
			this.getDao().remove(this.getDao().findCommentByUid(comment.getUid()));
		} else {
			LOG.warn("L'uid du commentaire à supprimer n'est pas présent");
		}

	}

	@Override
	public void deleteTask(final Task task) throws BusinessException, TechnicalException {
		if (task.getUid() != null) {
			this.getDao().remove(this.getDao().findTaskByUid(task.getUid()));
		} else {
			LOG.warn("L'uid de la tâche à supprimer n'est pas définie");
		}

	}

	@Override
	public Comments getComments(final Task task) throws BusinessException, TechnicalException {
		if (task.getUid() != null) {
			final com.psytech.app.task.entity.implementation.Task tmpTask = this.getDao().findTaskByUid(task.getUid());
			if (tmpTask != null) {
				final Comments comments = FACTORY.createGetCommentsResponseComments();
				for (final com.psytech.app.task.entity.implementation.Comment taskComment : tmpTask.getComments()) {
					comments.getComment().add(this.getJaxBComment(taskComment));
				}
				return comments;
			} else {
				LOG.warn("Aucune tâche ne correspond à l'uid : " + task.getUid()
						+ " impossible de récupérer les commentaires associés");
				throw new BusinessException("Aucune tâche ne correspond à l'uid : " + task.getUid()
						+ " impossible de récupérer les commentaires associés", -1);
			}
		} else {
			LOG.warn("L'uid de la tâche n'est pas défini, impossible de récupérer les commentaires associés");
			throw new BusinessException(
					"L'uid de la tâche n'est pas défini,  impossible de récupérer les commentaires associés", -1);
		}
	}

	@Override
	public Tasks getTasks(final User user, final Task task) throws BusinessException, TechnicalException {
		if (user != null && task == null) {
			// Tâches associées à l'utilisateur
			if (user.getUid() == null)
				throw new BusinessException("L'uid doit être connue pour ratacher l'utilisateur", -1);
			final com.psytech.app.task.entity.implementation.User existingUser = this.getDao().findUserByUid(
					user.getUid());
			final List<com.psytech.app.task.entity.implementation.Task> tasks = this.getDao().getAffectedTasks(
					existingUser);
			final Tasks tasksResponse = FACTORY.createGetTasksResponseTasks();
			for (final com.psytech.app.task.entity.implementation.Task existingTask : tasks) {
				tasksResponse.getTask().add(this.getJaxBTask(existingTask));
			}
			return tasksResponse;
		} else if (user == null && task != null) {
			final List<com.psytech.app.task.entity.implementation.Task> tasks = this.getDao().findTasks(
					this.getEntityTask(task));
			final Tasks tasksResponse = FACTORY.createGetTasksResponseTasks();
			for (final com.psytech.app.task.entity.implementation.Task existingTask : tasks) {
				tasksResponse.getTask().add(this.getJaxBTask(existingTask));
			}
			return tasksResponse;
		} else if (user != null && task != null) {
			final Tasks tasksResponse = FACTORY.createGetTasksResponseTasks();
			if (user.getUid() == null && this.taskIsEmpty(task)) {
				// Renvoie toutes les tâches non affectées
				for (final com.psytech.app.task.entity.implementation.Task unaffectedTask : this.getDao()
						.getUnaffectedTask()) {
					tasksResponse.getTask().add(this.getJaxBTask(unaffectedTask));
				}
			} else {
				final com.psytech.app.task.entity.implementation.User existingUser = this.getDao().findUserByUid(
						user.getUid());
				final List<com.psytech.app.task.entity.implementation.Task> affectedTasks = this.getDao()
						.getAffectedTasks(existingUser);
				final List<com.psytech.app.task.entity.implementation.Task> createdTasktasks = this.getDao().findTasks(
						this.getEntityTask(task));
				final Collection<com.psytech.app.task.entity.implementation.Task> allRelatedTask = CollectionUtils
						.union(affectedTasks, createdTasktasks);
				for (final com.psytech.app.task.entity.implementation.Task existingTask : allRelatedTask) {
					tasksResponse.getTask().add(this.getJaxBTask(existingTask));
				}
			}
			return tasksResponse;
		} else {
			final Tasks tasksResponse = FACTORY.createGetTasksResponseTasks();
			for (final com.psytech.app.task.entity.implementation.Task existingTask : this.getDao().getAllTask()) {
				tasksResponse.getTask().add(this.getJaxBTask(existingTask));
			}
			return tasksResponse;
		}
	}

	@Override
	public Users getUsers(final User user) throws BusinessException, TechnicalException {
		final com.psytech.app.task.entity.implementation.User userToFind;
		if (user == null) {
			userToFind = new com.psytech.app.task.entity.implementation.User();
		} else {
			userToFind = this.getEntityUser(user);
		}
		final Users users = FACTORY.createGetUsersResponseUsers();
		for (final com.psytech.app.task.entity.implementation.User entity : this.getDao().findUsers(userToFind)) {
			users.getUser().add(this.getJaxBUser(entity));
		}
		return users;
	}

	@Override
	public Task saveTask(final Task task) throws BusinessException, TechnicalException {
		if (StringUtils.isEmpty(task.getUid())) {
			LOG.debug("Creation d'une t�che");
			if (task.getCreator() == null || StringUtils.isEmpty(task.getCreator().getUid())) {
				LOG.warn("Pas de créateur correctement spécifié. Création de la tâche impossible");
				throw new BusinessException("Veuillez sp�cifier un créateur", -1);
			}
			com.psytech.app.task.entity.implementation.Task entityTask = this.getEntityTask(task);
			entityTask.setUid(UUID.randomUUID().toString());
			final List<com.psytech.app.task.entity.implementation.User> usersToAffect = new ArrayList<com.psytech.app.task.entity.implementation.User>(
					entityTask.getUsers());
			LOG.debug("userToAffect : " + usersToAffect.size());
			entityTask.getUsers().clear();
			entityTask.setCreator(this.getDao().findUserByUid(entityTask.getCreator().getUid()));
			this.getDao().create(entityTask);
			for (final com.psytech.app.task.entity.implementation.User affectedUser : usersToAffect) {
				try {
					LOG.debug("Recherche de " + affectedUser.getUid());
					final com.psytech.app.task.entity.implementation.User attachedUser = this.getDao().findUserByUid(
							affectedUser.getUid());
					if (attachedUser != null) {
						LOG.debug("Affectation de " + attachedUser.getUid() + " à " + entityTask.getUid());
						entityTask.getUsers().add(attachedUser);
						attachedUser.getAffectedTasks().add(entityTask);
						this.getDao().update(attachedUser);
					} else {
						LOG.debug("Pas de user correspondant en base");
					}
				} catch (final Exception exception) {
					LOG.error("Impossible de r�cup�rer le user à affecter.", exception);
				}
			}
			entityTask.getCreator().getCreatedTasks().add(entityTask);
			entityTask = (com.psytech.app.task.entity.implementation.Task) this.getDao().update(entityTask);
			LOG.debug("UID de la tâche créée = " + entityTask.getUid());
			LOG.debug("Nombre d'utilisateur affecté : " + entityTask.getUsers().size());
			return this.getJaxBTask(entityTask);
		} else {
			LOG.debug("Mise à jour d'une t�che");
			com.psytech.app.task.entity.implementation.Task existingTask = existingTask = this.getDao().findTaskByUid(
					task.getUid());
			if (task.getName() != null) {
				existingTask.setName(task.getName());
			}
			if (task.getDescription() != null)
				if (task.getDescription().isNil()) {
					existingTask.setDescription(null);
				} else {
					existingTask.setDescription(task.getDescription().getValue());
				}
			if (task.getExpectedEndDate() != null)
				if (task.getExpectedEndDate().getValue() != null && !task.getExpectedEndDate().isNil()) {
					existingTask.setExpectedEndDate(task.getExpectedEndDate().getValue().toGregorianCalendar()
							.getTime());
				} else if (task.getExpectedEndDate().isNil()) {
					existingTask.setExpectedEndDate(null);
				}
			if (task.getExpectedStartDate() != null)
				if (task.getExpectedStartDate().getValue() != null && !task.getExpectedStartDate().isNil()) {
					existingTask.setExpectedStartDate(task.getExpectedStartDate().getValue().toGregorianCalendar()
							.getTime());
				} else if (task.getExpectedStartDate().isNil()) {
					existingTask.setExpectedStartDate(null);
				}
			/*if ((task.getCreator() != null) && !existingTask.getCreator().getUid().equals(task.getCreator().getUid())) {
				existingTask.setCreator(getDao().findUserByUid(task.getCreator().getUid()));
				existingTask.getCreator().getCreatedTasks().add(existingTask);
				getDao().save(existingTask.getCreator());
			}*/
			if (task.getPriority() != null) {
				existingTask.setPriority(task.getPriority().value());
			}
			if (task.getStatus() != null) {
				existingTask.setStatus(task.getStatus().value());
			}
			// Utilisateurs associ�e en base
			final Map<String, com.psytech.app.task.entity.implementation.User> inDataBaseAffectedUsersMap = this
					.getEntityUserListUid(existingTask.getUsers());

			final List<User> userToAffect = new ArrayList<User>();

			for (final User user : task.getAffectedUsers()) {
				LOG.debug("Nom Utilisateur : " + user.getFirstName() + " " + user.getUid());
				if (!inDataBaseAffectedUsersMap.containsKey(user.getUid())) {
					userToAffect.add(user);
				} else {
					inDataBaseAffectedUsersMap.remove(user.getUid());
				}
			}
			for (final String uid : inDataBaseAffectedUsersMap.keySet()) {
				final com.psytech.app.task.entity.implementation.User user = inDataBaseAffectedUsersMap.get(uid);
				user.getAffectedTasks().remove(existingTask);
				existingTask.getUsers().remove(user);
				this.getDao().update(user);
			}
			for (final User user : userToAffect) {
				final com.psytech.app.task.entity.implementation.User tmpUser = this.getDao().findUserByUid(
						user.getUid());
				if (tmpUser == null)
					throw new TechnicalException("L'utilisateur d'uid " + user.getUid() + " n'existe pas", -1);
				tmpUser.getAffectedTasks().add(existingTask);
				this.getDao().update(tmpUser);
				existingTask.getUsers().add(tmpUser);
			}

			try {
				this.getDao().update(existingTask);
			} catch (final Exception e) {
				LOG.error("", e);
				throw new TechnicalException(e.getMessage(), -1);
			}
			LOG.debug("Mise à jour de la tâche éfféctuée");
			return this.getJaxBTask(existingTask);
		}
	}

	@Override
	public User saveUser(final User user) throws BusinessException, TechnicalException {
		if (user.getUid() == null) {
			LOG.debug("Creation d'un utilisateur");
			final com.psytech.app.task.entity.implementation.User userToCreate = this.getEntityUser(user);
			userToCreate.setUid(UUID.randomUUID().toString());
			this.getDao().create(userToCreate);
			return this.getJaxBUser(userToCreate);
		} else {
			LOG.debug("Mise à jour d'un utilisateur");
			final com.psytech.app.task.entity.implementation.User existingUser = this.getDao().findUserByUid(
					user.getUid());
			if (user.getLogin() != null) {
				existingUser.setLogin(user.getLogin());
			}
			if (user.getNickName() != null)
				if (user.getNickName().isNil()) {
					existingUser.setNickname(null);
				} else {
					existingUser.setNickname(user.getNickName().getValue());
				}
			if (user.getFirstName() != null) {
				existingUser.setFirstname(user.getFirstName());
			}
			if (user.getLastName() != null) {
				existingUser.setLastname(user.getLastName());
			}
			if (user.getPassword() != null) {
				existingUser.setPassword(user.getPassword());
			}
			this.getDao().update(existingUser);
			return this.getJaxBUser(existingUser);
		}
	}

	@Override
	public void deleteUser(final User user) {
		if (user.getUid() != null) {
			final com.psytech.app.task.entity.implementation.User userToDelete = this.getDao().findUserByUid(
					user.getUid());
			this.getDao().remove(userToDelete);
		} else {
			LOG.warn("L'uid de l'utilisateur � supprimer n'est pas d�fini");
		}

	}

	/*
	 * Renvoie une entité correspondant au jaxb passé en paramètre.
	 */
	private com.psytech.app.task.entity.implementation.User getEntityUser(final User user) {
		final com.psytech.app.task.entity.implementation.User entityUser = new com.psytech.app.task.entity.implementation.User();
		entityUser.setUid(user.getUid());
		entityUser.setFirstname(user.getFirstName());
		entityUser.setLastname(user.getLastName());
		entityUser.setPassword(user.getPassword());
		entityUser.setLogin(user.getLogin());
		return entityUser;
	}

	private User getJaxBUser(final com.psytech.app.task.entity.implementation.User user) {
		final User jaxbUser = FACTORY.createUser();
		jaxbUser.setFirstName(user.getFirstname());
		jaxbUser.setLastName(user.getLastname());
		jaxbUser.setLogin(user.getLogin());
		if (user.getNickname() != null) {
			jaxbUser.setNickName(FACTORY.createUserNickName(user.getNickname()));
		}
		jaxbUser.setUid(user.getUid());
		return jaxbUser;
	}

	private com.psytech.app.task.entity.implementation.Task getEntityTask(final Task task) {
		final com.psytech.app.task.entity.implementation.Task entityTask = new com.psytech.app.task.entity.implementation.Task();
		entityTask.setUid(task.getUid());
		entityTask.setName(task.getName());
		if (task.getDescription() != null && !task.getDescription().isNil()) {
			entityTask.setDescription(task.getDescription().getValue());
		}
		if (task.getExpectedEndDate() != null && !task.getExpectedEndDate().isNil()) {
			entityTask.setExpectedEndDate(task.getExpectedEndDate().getValue().toGregorianCalendar().getTime());
		}
		if (task.getExpectedStartDate() != null && !task.getExpectedStartDate().isNil()) {
			entityTask.setExpectedStartDate(task.getExpectedStartDate().getValue().toGregorianCalendar().getTime());
		}
		if (task.getPriority() != null) {
			entityTask.setPriority(task.getPriority().value());
		}
		if (task.getStatus() != null) {
			entityTask.setStatus(task.getStatus().value());
		}
		LOG.debug("Utilisateur à affecter : " + task.getAffectedUsers().size());
		for (final User affectedUser : task.getAffectedUsers()) {
			entityTask.getUsers().add(this.getEntityUser(affectedUser));
		}
		if (task.getCreator() != null) {
			entityTask.setCreator(this.getEntityUser(task.getCreator()));
		}
		return entityTask;
	}

	private Task getJaxBTask(final com.psytech.app.task.entity.implementation.Task task) {
		final Task jaxBTask = FACTORY.createTask();
		jaxBTask.setName(task.getName());
		jaxBTask.setStatus(StatusType.fromValue(task.getStatus()));
		jaxBTask.setPriority(PriorityType.fromValue(task.getPriority()));
		jaxBTask.setUid(task.getUid());
		if (task.getDescription() != null) {
			jaxBTask.setDescription(FACTORY.createTaskDescription(task.getDescription()));
		}
		if (task.getExpectedEndDate() != null) {
			final GregorianCalendar calendar = new GregorianCalendar();
			calendar.setTime(task.getExpectedEndDate());
			try {
				jaxBTask.setExpectedEndDate(FACTORY.createTaskExpectedEndDate(DatatypeFactory.newInstance()
						.newXMLGregorianCalendar(calendar)));
			} catch (final DatatypeConfigurationException exception) {
				LOG.error("Impossible de convertir la date en XML", exception);
			}
		}
		if (task.getExpectedStartDate() != null) {
			final GregorianCalendar calendar = new GregorianCalendar();
			calendar.setTime(task.getExpectedStartDate());
			try {
				jaxBTask.setExpectedStartDate(FACTORY.createTaskExpectedStartDate(DatatypeFactory.newInstance()
						.newXMLGregorianCalendar(calendar)));
			} catch (final DatatypeConfigurationException exception) {
				LOG.error("Impossible de convertir la date en XML", exception);
			}
		}
		for (final com.psytech.app.task.entity.implementation.User user : task.getUsers()) {
			jaxBTask.getAffectedUsers().add(this.getJaxBUser(user));
		}
		jaxBTask.setCreator(this.getJaxBUser(task.getCreator()));
		LOG.debug("Object correctement transform�");
		return jaxBTask;
	}

	/*
	 * 
	 */
	private com.psytech.app.task.entity.implementation.Comment getEntityComment(final Comment comment) {
		final com.psytech.app.task.entity.implementation.Comment entityComment = new com.psytech.app.task.entity.implementation.Comment();
		entityComment.setUid(comment.getUid());
		entityComment.setText(comment.getText());
		entityComment.setWriter(this.getEntityUser(comment.getWriter()));
		// TODO creationDate ?
		entityComment.setTask(this.getEntityTask(comment.getTask()));
		return entityComment;
	}

	private Comment getJaxBComment(final com.psytech.app.task.entity.implementation.Comment comment) {
		final Comment jaxBComment = FACTORY.createComment();
		jaxBComment.setUid(comment.getUid());
		jaxBComment.setText(comment.getText());
		jaxBComment.setTask(this.getJaxBTask(comment.getTask()));
		jaxBComment.setWriter(this.getJaxBUser(comment.getWriter()));
		if (comment.getCreationDate() != null) {
			final GregorianCalendar calendar = new GregorianCalendar();
			calendar.setTime(comment.getCreationDate());
			try {
				jaxBComment.setCreationDate(DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar));
			} catch (final DatatypeConfigurationException exception) {
				LOG.error("Impossible de convertir la date en XML", exception);
			}
		}
		return jaxBComment;
	}

	/*
	 * Retourne une map Uid->Entité correspondante la liste passée en paramètre
	 */
	private Map<String, com.psytech.app.task.entity.implementation.User> getEntityUserListUid(
			final List<com.psytech.app.task.entity.implementation.User> entities) {
		final Map<String, com.psytech.app.task.entity.implementation.User> map = new HashMap<String, com.psytech.app.task.entity.implementation.User>();
		for (final com.psytech.app.task.entity.implementation.User user : entities) {
			map.put(user.getUid(), user);
		}
		return map;

	}

	public boolean taskIsEmpty(final Task task) {
		if (task.getUid() != null)
			return false;
		if (task.getCreator() != null)
			return false;
		if (task.getAffectedUsers().size() != 0)
			return false;
		if (task.getDescription() != null)
			return false;
		if (task.getExpectedEndDate() != null)
			return false;
		if (task.getExpectedStartDate() != null)
			return false;
		if (task.getName() != null)
			return false;
		if (task.getPriority() != null)
			return false;
		if (task.getStatus() != null)
			return false;
		return true;

	}

}
